#!/usr/bin/env python
# Copyright (c) 2006 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Extracts meta information from Google Base feeds.

This modules makes an unauthenticated HTTP connection to Google Base tow
get meta information and make it available in a convenient form.
"""

import urllib
import urllib2
import urlparse
import httplib
from ProxyHTTPConnection import ProxyHTTPSConnection, GbaseHTTPErrorHandler
from xml.dom import minidom

GBASE_SERVER = 'base.google.com'
BASE_URL = "http://base.google.com/"
AUTH_URL_ROOT = "https://www.google.com/accounts/ClientLogin"
GM_NAMESPACE_URI = "http://base.google.com/ns-metadata/1.0"
G_NAMESPACE_URI = "http://base.google.com/ns/1.0"
ATOM_NAMESPACE_URI = "http://www.w3.org/2005/Atom"
PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth'
PROXY_HOST = ''
PROXY_PORT = None

class GoogleBaseService(object):
  """Communicates with google base feeds.
  """

  def __init__(self, user, passwd, base_url = BASE_URL):
    """Creates a service object and initializez it with the developer key.

    Args:
      base_url: Google Base server and path to connect to, used as the base url
                for connecting to the feeds.
                (default: http://base.google.com/)
    """
    self._base_url = urlparse.urljoin(base_url, "/base/feeds/")
    self.email = user
    self.password = passwd
    self.service = 'gbase'
    self.source = 'GbasePickle'
    self.account_type = 'HOSTED_OR_GOOGLE'
    self._opener = urllib2.build_opener(GbaseHTTPErrorHandler)
  
  def ProgrammaticLogin(self, captcha_token=None, captcha_response=None):
    """Authenticates the user and sets the GData Auth token.

    Login retreives a temporary auth token which must be used with all
    requests to GData services. The auth token is stored in the GData client
    object.

    Login is also used to respond to a captcha challenge. If the user's login
    attempt failed with a CaptchaRequired error, the user can respond by
    calling Login with the captcha token and the answer to the challenge.

    Args:
      captcha_token: string (optional) The identifier for the captcha challenge
                     which was presented to the user.
      captcha_response: string (optional) The user's answer to the captch 
                        challenge.

    Raises:
      CaptchaRequired if the login service will require a captcha response
      BadAuthentication if the login service rejected the username or password
      Error if the login service responded with a 403 different from the above
    """

    # Create a POST body containing the user's credentials.
    if captcha_token and captcha_response:
      # Send the captcha token and response as part of the POST body if the
      # user is responding to a captch challenge.
      request_body = urllib.urlencode({'Email': self.email,
                                       'Passwd': self.password,
                                       'accountType': self.account_type,
                                       'service': self.service,
                                       'source': self.source,
                                       'logintoken': captcha_token,
                                       'logincaptcha': captcha_response})
    else:
      request_body = urllib.urlencode({'Email': self.email,
                                       'Passwd': self.password,
                                       'accountType': self.account_type,
                                       'service': self.service,
                                       'source': self.source})

    # Open a connection to the authentication server.
    if PROXY_HOST == '':
      auth_connection = httplib.HTTPSConnection(GBASE_SERVER)
    else:
      auth_connection = ProxyHTTPSConnection(PROXY_HOST, PROXY_PORT)

    # Begin the POST request to the client login service.
    auth_connection.putrequest('POST', AUTH_URL_ROOT)
    # Set the required headers for an Account Authentication request.
    auth_connection.putheader('Content-type',
                              'application/x-www-form-urlencoded')
    auth_connection.putheader('Content-Length',str(len(request_body)))
    auth_connection.endheaders()

    auth_connection.send(request_body)

    # Process the response and throw exceptions if the login request did not
    # succeed.
    auth_response = auth_connection.getresponse()

    if auth_response.status == 200:
      response_body = auth_response.read()
      for response_line in response_body.splitlines():
        if response_line.startswith('Auth='):
          self.__auth_token = response_line.lstrip('Auth=')
          self.__auth_type = PROGRAMMATIC_AUTH_LABEL
          self._opener.addheaders = [('Authorization', '%s=%s' % (self.__auth_type, self.__auth_token))]
          # Get rid of any residual captcha information because the request
          # succeeded.
          self.__captcha_token = None
          self.__captcha_url = None

    elif auth_response.status == 403:
      response_body = auth_response.read()
      # Examine each line to find the error type and the captcha token and
      # captch URL if they are present.
      for response_line in response_body.splitlines():
        if response_line.startswith('Error='):
          error_line = response_line
        elif response_line.startswith('CaptchaToken='):
          self.__captcha_token = response_line.lstrip('CaptchaToken=')
        elif response_line.startswith('CaptchaUrl='):
          self.__captcha_url = 'https://www.google.com/accounts/Captcha%s' % (
                               response_line.lstrip('CaptchaUrl='))
      

      # Raise an exception based on the error type in the 403 response.
      # In cases where there was no captcha challenge, remove any previous
      # captcha values.
      if error_line == 'Error=CaptchaRequired':
        raise CaptchaRequired, 'Captcha Required'
      elif error_line == 'Error=BadAuthentication':
        self.__captcha_token = None
        self.__captcha_url = None
        raise BadAuthentication, 'Incorrect username or password'
      else:
        self.__captcha_token = None
        self.__captcha_url = None
        raise Error, 'Server responded with a 403 code'
    
  def loadObjStr(self, title):
    feed = self.run_query("items", bq='[item type:Notes] [tag:PyPersistence] [title:%s]' % title)
    feed.normalize()
    return feed
  
  def loadObjbyID(self, obj_id):
    
    handle = self._opener.open(obj_id)
    try:
      feed = minidom.parse(handle)
      feed.normalize()
      return feed
    finally:
      handle.close()

  def insertObjStr(self, data):
    feed, info = self.post_data('items', data)
    feed.normalize()
    return feed, info.get('Location', '')

  def updateObj(self, obj_id, data):
    feed, info = self.post_data('items', data, {'X-HTTP-Method-Override':'PUT'}, obj_id=obj_id)
    feed.normalize()
    return feed

  def deleteObj(self, obj_id):
    req = urllib2.Request(url=obj_id, data='')
    req.add_header('X-HTTP-Method-Override', 'DELETE')
    handle = self._opener.open(req)
    try:
      return handle.info().get('Status', '')
    finally:
      handle.close()
      
  def build_url(self, feed, url_params):
    parameters = [ (key.replace('_', '-'), url_params[key]) for key in url_params]
    relative_url = "%s?%s" % (feed, urllib.urlencode(parameters))
    url = urlparse.urljoin(self._base_url, relative_url)
    #print url
    return url

  def run_query(self, feed="items", **kargs):
    """Run a query on a feed.

    Args:
      feed: Google Base feed name (snippets, items, ...). "snippets" by default
      **kargs: feed parameters as keyword arguments (q, bq, max_results, ...)
        different feeds take different parameters. Replace - by _ in the
        parameter names.
    Returns:
      answer, as a DOM tree
    """
    parameters = [ (key.replace('_', '-'), kargs[key]) for key in kargs]
    relative_url = "%s?%s" % (feed, urllib.urlencode(parameters))
    url = urlparse.urljoin(self._base_url, relative_url)
    #print url
    handle = self._opener.open(url)
    try:
      return minidom.parse(handle)
    finally:
      handle.close()

  def post_data(self, feed, data, extra_headers=None, url_params={}, obj_id=None):
    '''
    '''
    if obj_id:
      url = obj_id
    else:
      url = self.build_url(feed, url_params)
    req = urllib2.Request(url=url, data=data)
    if extra_headers:
      for key in extra_headers.keys():
        req.add_header(key, extra_headers[key])
	
    req.add_header('Content-Type', 'application/atom+xml')		
    handle = self._opener.open(req)
    try:
      return minidom.parse(handle), handle.info()
    finally:
      handle.close()

_TOESCAPE = '|:="[]()*#<>\\+-'
def escape(to_escape):
  """Escape special characters in a string for inclusion in a Google Base query.

  Args:
    to_escape: a string

  Returns:
    the same string with a backslash in from on some special characters
  """
  def escapeChar(c):
    if c in _TOESCAPE:
      return "\\" + c
    else:
      return c
  return ''.join([escapeChar(c) for c in to_escape])
